package com.polidea.rxohosble2;

import com.polidea.rxohosble2.annotation.NonNull;

import com.polidea.rxohosble2.exceptions.BleCharacteristicNotFoundException;
import com.polidea.rxohosble2.exceptions.BleDescriptorNotFoundException;
import com.polidea.rxohosble2.exceptions.BleServiceNotFoundException;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;

import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.functions.Function;
import io.reactivex.functions.Predicate;
import ohos.bluetooth.ble.GattCharacteristic;
import ohos.bluetooth.ble.GattDescriptor;
import ohos.bluetooth.ble.GattService;

/**
 * Service discovery result containing list of services and characteristics within the services.
 */
public class RxBleDeviceServices {

    final List<GattService> bluetoothGattServices;

    public RxBleDeviceServices(List<GattService> bluetoothGattServices) {
        this.bluetoothGattServices = bluetoothGattServices;
    }

    /**
     * List of all GATT services supported by the device
     */
    public List<GattService> getBluetoothGattServices() {
        return bluetoothGattServices;
    }

    /**
     * Creates an observable emitting {@link GattService} with matching service UUID.
     * The observable completes after first emission.
     *
     * @param serviceUuid Service UUID to be found
     * @return Observable emitting matching services or error if hasn't been found.
     * @throws BleServiceNotFoundException if service with given UUID hasn't been found.
     */
    public Single<GattService> getService(@NonNull final UUID serviceUuid) {
        return Observable.fromIterable(bluetoothGattServices)
                .filter(new Predicate<GattService>() {

                    @Override
                    public boolean test(GattService bluetoothGattService) {
                        return bluetoothGattService.getUuid().equals(serviceUuid);
                    }
                })
                .firstElement()
                .switchIfEmpty(Single.<GattService>error(new BleServiceNotFoundException(serviceUuid)));
    }

    /**
     * Creates an observable emitting {@link GattCharacteristic} with matching characteristic UUID.
     * The observable completes after first emission.
     * <p>
     * The main assumption is that characteristics have unique UUID across all services as there is a traversal done
     * across all of them. For an alternative see RxBleDeviceServices#getCharacteristic(UUID)
     *
     * @param characteristicUuid Characteristic UUID to be found
     * @return Observable emitting matching characteristic or error if hasn't been found.
     * @throws BleCharacteristicNotFoundException if characteristic with given UUID hasn't been found.
     */
    public Single<GattCharacteristic> getCharacteristic(@NonNull final UUID characteristicUuid) {
        return Single.fromCallable(new Callable<GattCharacteristic>() {
            @Override
            public GattCharacteristic call() {
                for (GattService service : bluetoothGattServices) {
                    GattCharacteristic characteristic = service.getCharacteristic(characteristicUuid).get();
                    if (characteristic != null) {
                        return characteristic;
                    }
                }
                throw new BleCharacteristicNotFoundException(characteristicUuid);
            }
        });
    }

    /**
     * Creates an observable emitting {@link GattCharacteristic}s with matching service UUID and characteristic UUID.
     * The observable completes after first emission.
     *
     * @param characteristicUuid Characteristic UUID to be found
     * @param serviceUuid        Service UUID to search in
     * @return Observable emitting matching characteristic or error if hasn't been found.
     * @throws BleCharacteristicNotFoundException if characteristic with given UUID hasn't been found.
     * @see RxBleDeviceServices#getCharacteristic(UUID)
     */
    public Single<GattCharacteristic> getCharacteristic(@NonNull UUID serviceUuid, @NonNull final UUID characteristicUuid) {
        return getService(serviceUuid)
                .map(new Function<GattService, GattCharacteristic>() {
                    @Override
                    public GattCharacteristic apply(GattService bluetoothGattService) {
                        final GattCharacteristic characteristic = bluetoothGattService.getCharacteristic(characteristicUuid).get();

                        if (characteristic == null) {
                            throw new BleCharacteristicNotFoundException(characteristicUuid);
                        }
                        return characteristic;
                    }
                });
    }

    public Single<GattDescriptor> getDescriptor(final UUID characteristicUuid, final UUID descriptorUuid) {
        return getCharacteristic(characteristicUuid)
                .map(new Function<GattCharacteristic, GattDescriptor>() {
                    @Override
                    public GattDescriptor apply(GattCharacteristic bluetoothGattCharacteristic) {
                        final GattDescriptor descriptor = bluetoothGattCharacteristic.getDescriptor(descriptorUuid).get();

                        if (descriptor == null) {
                            throw new BleDescriptorNotFoundException(descriptorUuid);
                        }

                        return descriptor;
                    }
                });
    }

    public Single<GattDescriptor> getDescriptor(
            final UUID serviceUuid, final UUID characteristicUuid, final UUID descriptorUuid
    ) {
        return getService(serviceUuid)
                .map(new Function<GattService, GattCharacteristic>() {
                    @Override
                    public GattCharacteristic apply(GattService bluetoothGattService) {
                        return bluetoothGattService.getCharacteristic(characteristicUuid).get();
                    }
                })
                .map(new Function<GattCharacteristic, GattDescriptor>() {
                    @Override
                    public GattDescriptor apply(GattCharacteristic bluetoothGattCharacteristic) {
                        final GattDescriptor descriptor = bluetoothGattCharacteristic.getDescriptor(descriptorUuid).get();

                        if (descriptor == null) {
                            throw new BleDescriptorNotFoundException(descriptorUuid);
                        }

                        return descriptor;
                    }
                });
    }
}
